package shells

import (
	"bytes"
	"fmt"
	"github.com/fatih/color"
	"github.com/ihaiker/tfwrapper/internal/errors"
	"github.com/ihaiker/tfwrapper/internal/util"
	"io"
	"os"
	"os/exec"
	"strings"
)

func toEnvVarsList(envVarsAsMap map[string]string) []string {
	envs := os.Environ()
	for key, value := range envVarsAsMap {
		envs = append(envs, fmt.Sprintf("%s=%s", key, value))
	}
	return envs
}

// runShellCommand Run the given shell command
func runShellCommand(opts *Options) error {
	_, _, err := runShellCommandWithOutput(opts)
	return err
}

func loggerRun(opts *Options) {
	sb := bytes.NewBufferString(fmt.Sprintf("Running command: %s %s \n",
		color.HiGreenString(opts.command), color.HiYellowString(strings.Join(opts.args, " "))))

	if len(opts.environments) > 0 {
		sb.WriteString("Environment:\n")
		for key, val := range opts.environments {
			if util.ContainAny(key,
				"secret", "password", "passwd", "token", "key", "security") {
				sb.WriteString(fmt.Sprintf("    %s=%s%s\n", key, util.SafeSubstring(val, 0, 4), "******"))
			} else {
				sb.WriteString(fmt.Sprintf("    %s=%s\n", key, val))
			}
		}
	}
	if opts.workingDir != "" {
		sb.WriteString(fmt.Sprintf("Working directory: %s \n", color.HiYellowString(opts.workingDir)))
	}
	opts.log.Info(sb.String())
}

func runShellCommandWithOutput(opts *Options) (string, string, error) {
	command := opts.command
	args := opts.args
	workingDir := opts.workingDir
	envs := opts.environments
	allocatePseudoTty := opts.needPty

	loggerRun(opts)

	cmd := exec.Command(command, args...)
	cmd.Env = toEnvVarsList(envs)
	if workingDir == "" {
		cmd.Dir = opts.workingDir
	} else {
		cmd.Dir = workingDir
	}

	stdoutBuffer := bytes.NewBuffer(nil)
	stderrBuffer := bytes.NewBuffer(nil)

	var stdout io.Writer = stdoutBuffer
	var stderr io.Writer = stderrBuffer
	if opts.writer != nil {
		stdout = io.MultiWriter(opts.writer, stdoutBuffer)
		stderr = io.MultiWriter(opts.writer, stderrBuffer)
	}

	if allocatePseudoTty {
		if err := runCommandWithPTTY(opts, cmd, stdout, stderr); err != nil {
			return "", "", err
		}
	} else {
		cmd.Stdin = os.Stdin
		cmd.Stdout = stdout
		cmd.Stderr = stderr
		if err := cmd.Start(); err != nil {
			return "", "", errors.WithStackTrace(err)
		}
	}

	// Make sure to forward signals to the subcommand.
	cmdChannel := make(chan error) // used for closing the signals forwarder goroutine
	signalChannel := NewSignalsForwarder(forwardSignals, cmd, opts.log, cmdChannel)
	defer func() { _ = signalChannel.Close() }()

	err := cmd.Wait()
	cmdChannel <- err

	return strings.TrimSpace(stdoutBuffer.String()), strings.TrimSpace(stderrBuffer.String()), errors.WithStackTrace(err)
}
